/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package br.ufba.lasid.ds.replication;

import br.ufba.lasid.base.ModuleList;
import br.ufba.lasid.base.Moduletable;
import br.ufba.lasid.base.ResourceFactory;
import br.ufba.lasid.base.api.IModule;
import br.ufba.lasid.base.api.IResource;
import br.ufba.lasid.base.api.IResource.States;
import br.ufba.lasid.comm.HostList;
import br.ufba.lasid.comm.Hosttable;
import br.ufba.lasid.comm.api.ICommunicator;
import br.ufba.lasid.comm.api.IHost;
import br.ufba.lasid.comm.api.IHostFactory;
import br.ufba.lasid.comm.api.IReceiver;
import br.ufba.lasid.ds.fd.api.IFailureDetector;
import br.ufba.lasid.ds.replication.api.IReplicatedResourceManager;
import br.ufba.lasid.ds.replication.api.IReplicatedResourceMessageHandler;
import br.ufba.lasid.ds.replication.api.IReplicatedResourceModule;
import br.ufba.lasid.ds.replication.api.IReplicatedResourcePeriodicActionModule;
import br.ufba.lasid.ds.replication.api.IReplicatedResourcePeriodicDetectorModule;
import br.ufba.lasid.utils.Base;
import br.ufba.lasid.utils.DRBDResource;
import br.ufba.lasid.utils.ReplicaRole;
import br.ufba.lasid.utils.StateDRBD;
import br.ufba.lasid.virtualization.XenVMHandler;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author aliriosa
 */
public class ReplicatedResourceManager implements IReplicatedResourceManager {

    private IHost localhost;
    private IHost remotehost;
    private static final String CommunicatorName = "Communicator";
    private static final Log logger = LogFactory.getLog(ReplicatedResourceManager.class);
    private Moduletable moduletable = new Moduletable();
    private Hosttable hosttable = new Hosttable();
    private IFailureDetector detector;
    private DRBDResource resource;
    private XenVMHandler vm;

    public ReplicatedResourceManager() {
    }

    public ReplicatedResourceManager(DRBDResource resource) {
        this.resource = resource;
    }

    @Override
    public HostList getRegisteredHosts() {
        HostList hosts = new HostList();
        hosts.addAll(this.hosttable.values());
        return hosts;
    }

    @Override
    public String getName() throws Exception {
        return "ReplicatedResource";
    }

    @Override
    public void register(String hostID) throws Exception {
        this.register(hostID, false);
    }

    @Override
    public void register(String hostID, boolean islocal) throws Exception {
        if (isRegistered(hostID)) {
            return;
        }

        String a[] = hostID.split("@");

        if (a != null && a.length == 2) {

            vm = new XenVMHandler(a[0]);

            hostID = a[1];

            IHostFactory factory = (IHostFactory) ResourceFactory.getResourceFactory(IResource.Kinds.Host);

            IHost host = factory.create(hostID);

            register(host, islocal);
        } else {
            logger.error("Error on register ReplicatedResource host!");
        }
    }

    @Override
    public boolean isRegistered(String hostID) throws Exception {
        return hosttable.containsKey(hostID);
    }

    @Override
    public boolean isRegistered(IHost host) throws Exception {
        return isRegistered(host.getID());
    }

    @Override
    public void register(IHost host, boolean islocal) throws Exception {

        if (isRegistered(host)) {
            return;
        }

        host.setState(States.Undefined);
        host.setLocalhost(islocal);

        hosttable.put(host.getID(), host);

        if (host.isLocalhost()) {
            logger.info(host.getAddress() + " is a localhost.");
            this.localhost = host;
            this.localhost.setState(States.Up);
        } else {
            logger.info(host.getAddress() + " is a remotehost.");
            this.remotehost = host;
        }
        logger.info(host.getAddress() + " has been registered.");
    }

    @Override
    public void install(IReplicatedResourceModule module) throws Exception {
        moduletable.put(module.getModuleType().name(), module);
    }

    @Override
    public void install(ICommunicator communicator) throws Exception {
        moduletable.put(CommunicatorName, communicator);
    }

    @Override
    public void startup() throws Exception {
        ModuleList modules = new ModuleList();
        modules.addAll(this.moduletable.values());

        ICommunicator comm = getCommunicator();

        for (IModule module : modules) {

            if (module instanceof IReceiver && comm != null) {
//            if((module instanceof IFailureDetectorMessageHandler)){
                HostList hosts = this.getRegisteredHosts();
                for (IHost host : hosts) {
                    if (!host.isLocalhost()) {
                        comm.register(
                                (IReceiver) module, host.getID(), this.getName());
                    }//end if host isn't the localhost
                }//end for each host in registered host list
//            }//end if module is a message handle module
            }//end if module is instance of receiver and we have a communicator
            module.startup();
        }
    }

    @Override
    public ICommunicator getCommunicator() throws Exception {
        return (ICommunicator) moduletable.get(CommunicatorName);
    }

    @Override
    public IReplicatedResourcePeriodicActionModule getPeriodicAction() throws Exception {
        String moduleName = ModuleTypes.PeriodicAction.name();
        return (IReplicatedResourcePeriodicActionModule) moduletable.get(moduleName);
    }

    @Override
    public IReplicatedResourcePeriodicDetectorModule getPeriodicDetector() throws Exception {
        String moduleName = ModuleTypes.PeriodicDetector.name();
        return (IReplicatedResourcePeriodicDetectorModule) moduletable.get(moduleName);
    }

    @Override
    public IReplicatedResourceMessageHandler getMessageHandler() throws Exception {
        String moduleName = ModuleTypes.MessageHandler.name();
        return (IReplicatedResourceMessageHandler) moduletable.get(moduleName);
    }

    @Override
    public synchronized IHost getLocalhost() throws Exception {
        return this.localhost;
    }

    @Override
    public synchronized IHost getRemotehost() throws Exception {
        return this.remotehost;
    }

    @Override
    public IResource.States query(String resourceAddress) throws Exception {

        /**
         * We suppose that resource ID refers to a host.
         */
        String hostID = resourceAddress;

        boolean isHost = Utils.isAHostID(resourceAddress);

        /**
         * If resourceID isn't a hostID then we'll extract the hostID from the
         * resourceID.
         */
        if (!isHost) {
            hostID = Utils.getHostIDFromResourceAddress(resourceAddress);
        }

        /**
         * Get the host in the host table by ID.
         */
        IHost host = hosttable.get(hostID);

        /**
         * If there isn't the related host in the host table, then we'll
         * register it.
         */
        if (host == null) {
            register(hostID);
        }

        /**
         * If the resourceID is related to a host then we'll return the host
         * state.
         */
        if (isHost) {
            return host.getState();
        }

        /**
         * If the host is down then its resources are down too.
         */
        if (host.isDown()) {
            return States.Down;
        }

        /**
         * If the host is up and resourceID refers to a resource then we'll
         * return the resource state.
         */
        IResource resource = host.getResourcetable().get(resourceAddress);

        if (resource == null) {
            return States.Down;
        }

        return resource.getState();
    }

    @Override
    public boolean isUp(String resourceID) throws Exception {
        return States.Up.equals(query(resourceID));
    }

    @Override
    public boolean isDown(String resourceID) throws Exception {
        return States.Down.equals(query(resourceID));
    }

    @Override
    public boolean isUndefined(String resourceID) throws Exception {
        return States.Undefined.equals(query(resourceID));
    }

    @Override
    public void setPeriod(long period) throws Exception {
        this.getPeriodicAction().setPeriod(period);
    }

    @Override
    public void buildup() throws Exception {

        this.setRole();
        this.setState();

        if (this.getCommunicator() == null) {
            this.install(Base.Communicator);
        }

        if (this.getPeriodicDetector() == null) {
            this.install(new ReplicatedResourcePeriodicDetectorModule(this));
        }

        if (this.getPeriodicAction() == null) {
            this.install(new ReplicatedResourcePeriodicActionModule(this));
        }

        if (this.getMessageHandler() == null) {
            this.install(new ReplicatedResourceMessageHandler(this));
        }
    }

    @Override
    public void buildupAndStartup() throws Exception {
        buildup();
        startup();
    }

    @Override
    public Hosttable getHosttable() throws Exception {
        return this.hosttable;
    }

    @Override
    public IFailureDetector getDetector() {
        return this.detector;
    }

    @Override
    public void setDetector(IFailureDetector detector) {
        this.detector = detector;
    }

    @Override
    public DRBDResource getResource() {
        return resource;
    }

    @Override
    public void setResource(DRBDResource resource) {
        this.resource = resource;
    }

    @Override
    public XenVMHandler getVm() {
        return vm;
    }

    @Override
    public void setVm(XenVMHandler vm) {
        this.vm = vm;
    }

    @Override
    public String getVmPath() {
        return this.resource.getPath() + this.vm.getName();
    }

    @Override
    public String getLastCheckPoint() throws IOException {

        String c = "ls " + this.getVmPath() + "-OK-* | tail -1";

        System.out.println(c);

        Process p = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", c});

        BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));

        String pOut = in.readLine();

        return pOut;
    }

    @Override
    public void setRole() throws IOException {

        Process p = Runtime.getRuntime().exec("drbdadm role " + resource.getResource());
        BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String pOut = in.readLine();

        if (pOut != null) {
            pOut = pOut.toLowerCase();
            String a[] = pOut.split("/");
            if (a != null && a.length == 2) {
                String r = a[0];
                if (StringUtils.equalsIgnoreCase(ReplicaRole.Primary.toString(), r)) {

                    logger.info("Set to Primary");
                    this.resource.setRole(ReplicaRole.Primary);
                } else if (StringUtils.equalsIgnoreCase(ReplicaRole.Secondary.toString(), r)) {

                    logger.info("set to Secondary");
                    this.resource.setRole(ReplicaRole.Secondary);
                } else {
                    logger.error("Error on getting DRBD role!");
                }
            } else {
                logger.error("Error on getting DRBD role!");
            }
        } else {
            logger.error("Error on getting DRBD role!");
        }
    }

    @Override
    public void setState() throws IOException {

        Process p = Runtime.getRuntime().exec("drbdadm cstate " + resource.getResource());
        BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String pOut = in.readLine();

        if (pOut != null) {
            pOut = pOut.toLowerCase();
            if (StringUtils.isNotBlank(pOut)) {
                if (StringUtils.equalsIgnoreCase(StateDRBD.Connected.toString(), pOut)) {
                    logger.info("state Connected");
                    this.resource.setState(StateDRBD.Connected);
                } else if (StringUtils.equalsIgnoreCase(StateDRBD.Disconnected.toString(), pOut)) {
                    logger.info("state Disconnected");
                    this.resource.setState(StateDRBD.Disconnected);
                } else if (StringUtils.equalsIgnoreCase(StateDRBD.StandAlone.toString(), pOut)) {
                    logger.info("state StandAlone");
                    this.resource.setState(StateDRBD.StandAlone);
                } else if (StringUtils.equalsIgnoreCase(StateDRBD.WFConnection.toString(), pOut)) {
                    logger.info("state WFConnection");
                    this.resource.setState(StateDRBD.WFConnection);
                } else {
                    logger.error("Error on getting DRBD state!");
                }
            } else {
                logger.error("Error on getting DRBD state!");
            }
        } else {
            logger.error("Error on getting DRBD state!");
        }
    }
}
